package com.investmenttechnologyservices.services;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.Transaction;
import com.google.inject.Inject;
import com.investmenttechnologyservices.datadictionary.EntityMetadata;
import com.investmenttechnologyservices.datadictionary.FieldMetadata;
import com.investmenttechnologyservices.datadictionary.FieldService;
import com.investmenttechnologyservices.datadictionary.FieldType;
import com.investmenttechnologyservices.util.Util;

public class DatastoreHelper {
	
	private DatastoreService ds;
	
	@Inject
	DatastoreHelper(DatastoreService ds) {
		this.ds = ds;
	}
	
	public void writeEntities(PrintWriter w, String kind, List<Entity> entities) throws ServletException {
		checkNotNull(w);
		checkNotNull(kind);
		checkNotNull(entities);
		
		try {
			EntityMetadata entityMetadata = FieldService.getEntityMetadata(kind);
			checkNotNull(entityMetadata);
			
			w.append('[');
			boolean first = true;

			for (Entity entity : entities) {
				
				if (!first) {
					w.append(',');
				}
				
				w.append('{');
				// write the key.id or key.name
				writeId(w, entity);
				
				for (FieldMetadata fieldMetadata : entityMetadata.getFields().values()) {
					String name = fieldMetadata.getName();
					FieldType fieldType = fieldMetadata.getType();
					Object value  = entity.getProperty(name);
					Util.printJSON(w, name, fieldType, value, false);
				}
				
				String parent = entityMetadata.getParent();
				if (parent != null) {
					Key parentKey = entity.getParent();
					if (parentKey != null && parentKey.isComplete()) {
						if (entityMetadata.isParentLongId()) {
							Long id = parentKey.getId();
							Util.printJSON(w, "parent", FieldType.LONG, id, false);
						} else {
							String name = parentKey.getName();
							Util.printJSON(w, "parent", FieldType.STRING, name, false);
						}
					}
				}
				
				w.append('}');
				
				first = false;
			}

			w.append(']');
		} catch (Exception ex) {
			throw new ServletException(ex);
		}
	}
	
	public void writeEntity(PrintWriter w, String kind, Entity entity) throws ServletException {
		checkNotNull(w);
		checkNotNull(kind);
		checkNotNull(entity);
		
		try {
			EntityMetadata entityMetadata = FieldService.getEntityMetadata(kind);
			checkNotNull(entityMetadata);
			
			w.append('{');
			// write the key.id or key.name
			writeId(w, entity);
			
			for (FieldMetadata fieldMetadata : entityMetadata.getFields().values()) {
				String name = fieldMetadata.getName();
				FieldType fieldType = fieldMetadata.getType();
				Object value  = entity.getProperty(name);
				Util.printJSON(w, name, fieldType, value, false);
			}
			
			String parent = entityMetadata.getParent();
			if (parent != null) {
				Key parentKey = entity.getParent();
				if (parentKey != null && parentKey.isComplete()) {
					if (entityMetadata.isParentLongId()) {
						Long id = parentKey.getId();
						Util.printJSON(w, "parent", FieldType.LONG, id, false);
					} else {
						String name = parentKey.getName();
						Util.printJSON(w, "parent", FieldType.STRING, name, false);
					}
				}
			}
			
			w.append('}');
		} catch (Exception ex) {
			throw new ServletException(ex);
		}
	}

	public void findAll(String kind, HttpServletRequest req, HttpServletResponse res) throws ServletException {
		try {
			EntityMetadata entityMetadata = FieldService.getEntityMetadata(kind);
			checkNotNull(entityMetadata);
			
			Query q = new Query(entityMetadata.getKind());
			q.addSort("__key__", SortDirection.DESCENDING);
			Iterator<Entity> iterator = ds.prepare(q).asIterable().iterator();
			
			PrintWriter w = res.getWriter();
			//Writer w = new StringWriter();
			
			w.append('[');
			boolean first = true;
			while (iterator.hasNext()) {
				Entity entity = iterator.next();
			
				if (!first) {
					w.append(',');
				}
				
				w.append('{');
				// write the key.id or key.name
				writeId(w, entity);
				
				for (FieldMetadata fieldMetadata : entityMetadata.getFields().values()) {
					String name = fieldMetadata.getName();
					FieldType fieldType = fieldMetadata.getType();
					Object value  = entity.getProperty(name);
					Util.printJSON(w, name, fieldType, value, false);
				}
				
				String parent = entityMetadata.getParent();
				if (parent != null) {
					Key parentKey = entity.getParent();
					if (parentKey != null && parentKey.isComplete()) {
						if (entityMetadata.isParentLongId()) {
							Long id = parentKey.getId();
							Util.printJSON(w, "parent", FieldType.LONG, id, false);
						} else {
							String name = parentKey.getName();
							Util.printJSON(w, "parent", FieldType.STRING, name, false);
						}
					}
				}
				
				w.append('}');
				
				first = false;
			}

			w.append(']');
		} catch (Exception ex) {
			throw new ServletException(ex);
		}
	}
	
	public Map<String,Object> find(String kind, HttpServletRequest req, HttpServletResponse res) throws ServletException {
		try {
			return null;
		} catch (Exception ex) {
			throw new ServletException(ex);
		}
	}
	
	public void create(String kind, HttpServletRequest req, HttpServletResponse res) throws ServletException {
		try {
			EntityMetadata entityMetadata = FieldService.getEntityMetadata(kind);
			checkNotNull(entityMetadata);

			Collection<FieldMetadata> fields = entityMetadata.getFields().values();
			
			PrintWriter writer = res.getWriter();

			Entity entity = new Entity(entityMetadata.getKind());
			
			StringBuilder sb = new StringBuilder();

			boolean shouldCreate = false; 
			for (FieldMetadata fieldMetadata : fields) {
				// extract info from posted data
				String strValue = req.getParameter(fieldMetadata.getName());
				if (strValue != null) {
					// convert and set the value to the entity
					setValueToEntity(entity, fieldMetadata, strValue);
					appendJSON (sb, fieldMetadata, strValue);
					shouldCreate = true;
				}
			}
			
			// add timestamp
			entity.setProperty("created", new Date());
			
			if (shouldCreate) {
				DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
				Transaction tx = ds.beginTransaction();
				ds.put(entity);
				tx.commit();

				writer.print('{');

				// write the id
				writeId(writer, entity);
				
				// write the rest of the properties
				writer.print(sb.toString());
				
				writer.print('}');
			}

		} catch (Exception ex) {
			throw new ServletException(ex);
		}
	}
	


	private void writeId(Writer writer, Entity entity) throws IOException {
		Key key = entity.getKey();
		if (key.getId() != 0) {
			writer.append("\"id\":"+key.getId());
		} else if (key.getName() != null) {
			writer.append("\"id\":\""+key.getName()+"\"");
		}
	}

	public void appendJSON(StringBuilder sb, FieldMetadata fieldMetadata, String strValue) {
		sb.append(',');
		
		sb.append('\"');
		sb.append(fieldMetadata.getName());
		sb.append('\"');
		sb.append(':');
		
		switch (fieldMetadata.getType()) {
		case BOOL:
		case DOUBLE:
		case LONG:
			sb.append(strValue);
			break;
		case DATE:
		case STRING:
			sb.append('\"');
			sb.append(strValue);
			sb.append('\"');
			break;
		default:
			break;
		}
	}
	
	private static void setValueToEntity(Entity entity, FieldMetadata fieldMetadata, String strValue) {
		entity.setProperty(fieldMetadata.getName(), fieldMetadata.getType().stringToValue(strValue));
	}
}
